/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.catalina.authenticator;

import org.apache.catalina.Session;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.security.Principal;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * A class that represents entries in the cache of authenticated users.
 * This is necessary to make it available to
 * <code>AuthenticatorBase</code> subclasses that need it in order to perform
 * reauthentications when SingleSignOn is in use.
 *
 * @author B Stansberry, based on work by Craig R. McClanahan
 * @see SingleSignOn
 * @see AuthenticatorBase#reauthenticateFromSSO
 */
public class SingleSignOnEntry implements Serializable {

	private static final long serialVersionUID = 1L;

	// ------------------------------------------------------  Instance Fields

	protected String authType = null;

	protected String password = null;

	// Marked as transient so special handling can be applied to serialization
	protected transient Principal principal = null;

	protected ConcurrentMap<SingleSignOnSessionKey, SingleSignOnSessionKey> sessionKeys =
			new ConcurrentHashMap<SingleSignOnSessionKey, SingleSignOnSessionKey>();

	protected String username = null;

	protected boolean canReauthenticate = false;

	// ---------------------------------------------------------  Constructors

	/**
	 * Creates a new SingleSignOnEntry
	 *
	 * @param principal the <code>Principal</code> returned by the latest
	 *                  call to <code>Realm.authenticate</code>.
	 * @param authType  the type of authenticator used (BASIC, CLIENT_CERT,
	 *                  DIGEST or FORM)
	 * @param username  the username (if any) used for the authentication
	 * @param password  the password (if any) used for the authentication
	 */
	public SingleSignOnEntry(Principal principal, String authType,
	                         String username, String password) {

		updateCredentials(principal, authType, username, password);
	}

	// ------------------------------------------------------- Package Methods

	/**
	 * Adds a <code>Session</code> to the list of those associated with
	 * this SSO.
	 *
	 * @param sso     The <code>SingleSignOn</code> valve that is managing
	 *                the SSO session.
	 * @param session The <code>Session</code> being associated with the SSO.
	 */
	public void addSession(SingleSignOn sso, String ssoId, Session session) {
		SingleSignOnSessionKey key = new SingleSignOnSessionKey(session);
		SingleSignOnSessionKey currentKey = sessionKeys.putIfAbsent(key, key);
		if (currentKey == null) {
			// Session not previously added
			session.addSessionListener(sso.getSessionListener(ssoId));
		}
	}

	/**
	 * Removes the given <code>Session</code> from the list of those
	 * associated with this SSO.
	 *
	 * @param session the <code>Session</code> to remove.
	 */
	public void removeSession(Session session) {
		SingleSignOnSessionKey key = new SingleSignOnSessionKey(session);
		sessionKeys.remove(key);
	}

	/**
	 * Returns the HTTP Session identifiers associated with this SSO.
	 *
	 * @return The identifiers for the HTTP sessions that are current associated
	 * with this SSo entry
	 */
	public Set<SingleSignOnSessionKey> findSessions() {
		return sessionKeys.keySet();
	}

	/**
	 * Gets the name of the authentication type originally used to authenticate
	 * the user associated with the SSO.
	 *
	 * @return "BASIC", "CLIENT_CERT", "DIGEST", "FORM" or "NONE"
	 */
	public String getAuthType() {
		return this.authType;
	}

	/**
	 * Gets whether the authentication type associated with the original
	 * authentication supports reauthentication.
	 *
	 * @return <code>true</code> if <code>getAuthType</code> returns
	 * "BASIC" or "FORM", <code>false</code> otherwise.
	 */
	public boolean getCanReauthenticate() {
		return this.canReauthenticate;
	}

	/**
	 * Gets the password credential (if any) associated with the SSO.
	 *
	 * @return the password credential associated with the SSO, or
	 * <code>null</code> if the original authentication type
	 * does not involve a password.
	 */
	public String getPassword() {
		return this.password;
	}

	/**
	 * Gets the <code>Principal</code> that has been authenticated by the SSO.
	 *
	 * @return The Principal that was created by the authentication that
	 * triggered the creation of the SSO entry
	 */
	public Principal getPrincipal() {
		return this.principal;
	}

	/**
	 * Gets the user name provided by the user as part of the authentication
	 * process.
	 *
	 * @return The user name that was authenticated as part of the
	 * authentication that triggered the creation of the SSO entry
	 */
	public String getUsername() {
		return this.username;
	}

	/**
	 * Updates the SingleSignOnEntry to reflect the latest security
	 * information associated with the caller.
	 *
	 * @param principal the <code>Principal</code> returned by the latest
	 *                  call to <code>Realm.authenticate</code>.
	 * @param authType  the type of authenticator used (BASIC, CLIENT_CERT,
	 *                  DIGEST or FORM)
	 * @param username  the username (if any) used for the authentication
	 * @param password  the password (if any) used for the authentication
	 */
	public synchronized void updateCredentials(Principal principal, String authType,
	                                           String username, String password) {
		this.principal = principal;
		this.authType = authType;
		this.username = username;
		this.password = password;
		this.canReauthenticate = (HttpServletRequest.BASIC_AUTH.equals(authType) ||
				HttpServletRequest.FORM_AUTH.equals(authType));
	}

	private void writeObject(ObjectOutputStream out) throws IOException {
		out.defaultWriteObject();
		if (principal instanceof Serializable) {
			out.writeBoolean(true);
			out.writeObject(principal);
		} else {
			out.writeBoolean(false);
		}
	}

	private void readObject(ObjectInputStream in) throws IOException,
			ClassNotFoundException {
		in.defaultReadObject();
		boolean hasPrincipal = in.readBoolean();
		if (hasPrincipal) {
			principal = (Principal) in.readObject();
		}
	}
}
